package org.barad.voiceuserinterface;

import static org.barad.voiceuserinterface.VoiceInterfaceProperties.PROVIDE_SPEECH_RECOGNITION_FEEDBACK;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.log4j.Logger;

import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;

import edu.cmu.sphinx.frontend.util.Microphone;
import edu.cmu.sphinx.recognizer.Recognizer;
import edu.cmu.sphinx.result.Result;
import edu.cmu.sphinx.util.props.ConfigurationManager;
import edu.cmu.sphinx.util.props.PropertyException;

/**
 * This class provides speech generation and speech recognition
 * services to the voice widgets. It is a singleton.
 * @author svetoslavganov
 *
 */
public class SpeechProcessingManager {
	private static Logger log = Logger.getLogger(SpeechProcessingManager.class);
	private static SpeechProcessingManager speechProcessingManager;
    private static Voice voice;
    private static Recognizer recognizer;
    private static Microphone microphone;
    private static VoiceManager voiceManager;

    /**
     * Factory method for access to the SpeechProcessingManager.
     * If an instance of this class exist then it is returned, 
     * otherwise a new one is instantiated.
     * @return The singleton instance of this class
     */
    public static SpeechProcessingManager getInstance() {
    	if (speechProcessingManager == null) {
    		speechProcessingManager = new SpeechProcessingManager();
    	}
    	return speechProcessingManager;
    }
    
    /**
     * Protected construcor that initializes the speech
     * recognition and speech generation modules
     */
    protected SpeechProcessingManager() {
    	voiceManager = VoiceManager.getInstance();
    	//initialize the speech generator
    	voice = voiceManager.getVoice("kevin16");
    	if (voice == null) {
    		String message = "Cannot find a voice named " + voice + ".  Please specify a different voice.";
    		log.error(message);
    		throw new IllegalArgumentException(message);
    	}
    	voice.allocate();
    	//initialize the speech recognizer
        try {
        	URL url = this.getClass().getResource("/org/barad/voiceuserinterface/speechrecognizer.config.xml");
        	ConfigurationManager cm = new ConfigurationManager(url);
        	recognizer = (Recognizer) cm.lookup("recognizer");
        	microphone = (Microphone) cm.lookup("microphone");
        	recognizer.allocate();
        } catch (MalformedURLException mue) {
        	log.error(mue);
        } catch (InstantiationException ie) {
        	log.error(ie);
        } catch (PropertyException pe) {
        	log.error(pe);
        } catch (IOException ioe) {
        	log.error(ioe);
        }
    }
    
    /**
     * Pronounces a specific phrase at specific speed
     * @param phrase The phrase to be spoken
     * @param wordsPerMinute The speed of speech
     */
    public void speak(String phrase, float wordsPerMinute) {
    	float temp = voice.getRate();
    	voice.setRate(wordsPerMinute);
    	voice.speak(phrase);
    	voice.setRate(temp);
    }
      
    /**
     * Recognizes a phrase speech phrase spoken in the
     * microphone. 
     * NOTE: This method blocks while the user is speeking
     * @return The string representation of the spoken phrase
     */
    public String listen() {
    	String resultText = null;
    	if (microphone.startRecording()) {
    		microphone.clear();
    		Result result = recognizer.recognize();
    		if (result != null) {
    			resultText = result.getBestResultNoFiller();
     			if (PROVIDE_SPEECH_RECOGNITION_FEEDBACK) {
     				log.info("You said: " + resultText + "\n");
     			}
     		} 
    		microphone.stopRecording();
     	} else {
     		log.error("Microphone inialization failed");
    	}
    	return resultText;
    }
    
    /**
     * Deallocates resources allocated by the speech
     * generation and recognition modules.
     */
    @Override
    protected void finalize() {
    	voice.deallocate();
    	recognizer.deallocate();
    }
}